/*
Copyright (c) 2012, Janez Žemva
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

o Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
o The names of contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef ETL_VECTOR_HPP
# define ETL_VECTOR_HPP
# pragma once

#include <cassert>

#include <cmath>

#include <initializer_list>

#include <iterator>

#include <ostream>

#include "utility.hpp"

namespace etl
{

template <typename, ::std::size_t, typename>
struct vector_expression;

template <typename T, ::std::size_t N>
struct vector : vector_expression<T, N, vector<T, N> >
{
  using value_type = T;
  using size_type = ::std::size_t;

  using difference_type = ::std::ptrdiff_t;

  using iterator = T*;
  using const_iterator = T const*;

  using reverse_iterator = ::std::reverse_iterator<iterator>;
  using const_reverse_iterator = ::std::reverse_iterator<const_iterator>;

  static constexpr auto dimension = N;

  constexpr vector() = default;

  constexpr vector(vector const&) = default;
  constexpr vector(vector&&) = default;

  template <typename U,
    typename = typename ::std::enable_if<
      !::std::is_same<vector, typename ::std::decay<U>::type>{}
    >::type
  >
  vector(U&& u) noexcept
  {
    *this = ::std::forward<U>(u);
  }

  template <typename ...U,
    typename = typename ::std::enable_if<detail::all_of<
      ::std::is_same<T, typename ::std::decay<U>::type>...>{} &&
      (N == sizeof...(U))
    >::type
  >
  constexpr vector(U&& ...args) noexcept : data_{::std::forward<U>(args)...}
  {
  }

  template <typename E>
  vector(vector_expression<T, N, E>&& other) noexcept
  {
    *this = ::std::forward<E>(other);
  }

  constexpr const_iterator cbegin() const noexcept { return data_; }
  constexpr const_iterator cend() const noexcept { return data_ + N; }

  iterator begin() noexcept { return data_; }
  iterator end() noexcept{ return data_ + N; }

  constexpr const_iterator begin() const noexcept { return data_; }
  constexpr const_iterator end() const noexcept { return data_ + N; }

  constexpr value_type const* cdata() const noexcept { return data_; }

  value_type* data() noexcept { return data_; }
  constexpr value_type const* data() const noexcept { return data_; }

  static constexpr size_type size() noexcept { return N; }

  vector& operator=(vector const&) = default;
  vector& operator=(vector&&) = default;

  vector& operator=(T const& v) noexcept
  {
    assign_constant(v, detail::make_indices<N>());

    return *this;
  }

  vector& operator=(::std::initializer_list<T> list) noexcept
  {
    assert(size() == list.size());
    assign_initializer<::std::initializer_list<T> >(
      ::std::begin(list), detail::make_indices<N>());

    return *this;
  }

  template <typename Container>
  typename ::std::enable_if<
    detail::is_container<T, Container>{},
    vector&
  >::type
  operator=(Container const& container) noexcept
  {
    assert(size() ==
      ::std::distance(::std::begin(container), ::std::end(container)));
    assign_initializer<Container>(
      ::std::begin(container), detail::make_indices<N>());

    return *this;
  }

  template <typename E>
  vector& operator=(vector_expression<T, N, E> const& rhs) noexcept
  {
    assign_expression(rhs, detail::make_indices<N>());

    return *this;
  }

  template <typename E>
  vector& operator+=(vector_expression<T, N, E> const& rhs) noexcept
  {
    add_expression(rhs, detail::make_indices<N>());

    return *this;
  }

  template <typename E>
  vector& operator-=(vector_expression<T, N, E> const& rhs) noexcept
  {
    subtract_expression(rhs, detail::make_indices<N>());

    return *this;
  }

  vector& operator*=(value_type const s) noexcept
  {
    scalar_multiply(s, detail::make_indices<N>());

    return *this;
  }

  constexpr T cget(size_type const i) const { return data_[i]; }

  T const& get(size_type const i) const noexcept { return data_[i]; }
  T& get(size_type const i) noexcept { return data_[i]; }

  constexpr T operator()(size_type const i) const noexcept { return data_[i]; }
  T& operator()(size_type const i) noexcept { return data_[i]; }

  template<typename ...U>
  typename ::std::enable_if<
    detail::all_of<::std::is_same<
      typename ::std::decay<U>::type, T>...
    >{}
  >::type
  assign(U&& ...args) noexcept
  {
    static_assert(N == sizeof...(U),
      "wrong number of arguments in assignment");

    assign_constants(detail::make_indices<N>(), ::std::forward<U>(args)...);
  }

private:
  template <typename>
  struct is_vector : ::std::false_type { }; 

  template <typename U, ::std::size_t M>
  struct is_vector<vector<U, M> > : ::std::true_type { };

  template <typename vec_type, ::std::size_t ...Is, typename U, ::size_t M, typename E>
  typename ::std::enable_if<!is_vector<E>{}, vec_type>::type
  get_vec(vector_expression<U, M, E> const& e) noexcept
  {
    return vec_type{e(Is)...};
  }

  template <typename vec_type, ::std::size_t ..., typename U, ::size_t M, typename E>
  typename ::std::enable_if<is_vector<E>{}, vec_type const&>::type
  get_vec(vector_expression<U, M, E> const& e) noexcept
  {
    return *reinterpret_cast<vec_type const*>(
      static_cast<vector<U, M> const&>(e).cdata());
  }

  template <typename vec_type, ::std::size_t ..., typename U, ::size_t M, typename E>
  typename ::std::enable_if<is_vector<E>{}, vec_type&>::type
  get_vec(vector_expression<U, M, E>& e) noexcept
  {
    return *reinterpret_cast<vec_type*>(
      static_cast<vector<U, M>&>(e).data());
  }

  template<size_type... Is>
  void assign_constant(T const& v, detail::indices<Is...> const) noexcept
  {
    ::std::initializer_list<int>{(data_[Is] = v, 0)...};
  }

  template<size_type... Is, typename ...A>
  void assign_constants(detail::indices<Is...> const, A&& ...args) noexcept
  {
    ::std::initializer_list<int>{(data_[Is] = ::std::forward<A>(args), 0)...};
  }

  template<class C, size_type... Is>
  void assign_initializer(typename C::const_iterator i,
    detail::indices<Is...> const) noexcept
  {
    ::std::initializer_list<int>{(data_[Is] = *i++, 0)...};
  }

  template<size_type... Is, class E>
  void assign_expression(vector_expression<T, N, E> const& e,
    detail::indices<Is...> const) noexcept
  {
    ::std::initializer_list<int>{(data_[Is] = e(Is), 0)...};
  }

#if defined(__GNUC__) && !defined(__clang__)
  template<size_type... Is, class E>
  void add_expression(vector_expression<T, N, E> const& e,
    detail::indices<Is...>) noexcept
  {
    typedef value_type vec_type __attribute__ ((vector_size(
      detail::map_extent<E::dimension>{} * sizeof(value_type))));

    get_vec<vec_type, Is...>(*this) += get_vec<vec_type, Is...>(e);
  }
#else
  template<size_type... Is, class E>
  void add_expression(vector_expression<T, N, E> const& e,
    detail::indices<Is...> const) noexcept
  {
    [](...){}((data_[Is] += e(Is), 0)...);
  }
#endif

#if defined(__GNUC__) && !defined(__clang__)
  template<size_type... Is, class E>
  void subtract_expression(vector_expression<T, N, E> const& e,
    detail::indices<Is...>) noexcept
  {
    typedef value_type vec_type __attribute__ ((vector_size(
      detail::map_extent<E::dimension>{} * sizeof(value_type))));

    get_vec<vec_type, Is...>(*this) -= get_vec<vec_type, Is...>(e);
  }
#else
  template<size_type... Is, class E>
  void subtract_expression(vector_expression<T, N, E> const& e,
    detail::indices<Is...>) noexcept
  {
    ::std::initializer_list<int>{(data_[Is] -= e(Is), 0)...};
  }
#endif

#if defined(__GNUC__) && !defined(__clang__)
  template<size_type... Is>
  void scalar_multiply(T const s, detail::indices<Is...>) noexcept
  {
    typedef value_type vec_type __attribute__ ((vector_size(
      detail::map_extent<sizeof...(Is)>{} * sizeof(value_type))));

    get_vec<vec_type, Is...>(*this) *= s;
  }
#else
  template<size_type... Is>
  void scalar_multiply(T const s, detail::indices<Is...>) noexcept
  {
    ::std::initializer_list<int>{(data_[Is] *= s, 0)...};
  }
#endif

private:
#if (__ARM_NEON__) || defined(__SSE__) || defined(__SSE2__) ||\
  defined(__GNUC__)
  alignas(16) T data_[detail::map_extent<N>{}];
#else
  T data_[N];
#endif
};

template <typename>
struct is_vector : ::std::false_type { }; 

template <typename T, ::std::size_t N>
struct is_vector<vector<T, N> > : ::std::true_type { };

template <typename vec_type, ::std::size_t ...Is, typename U, ::size_t M, typename E>
typename ::std::enable_if<!is_vector<E>{}, vec_type>::type
get_vec(vector_expression<U, M, E> const& e)
{
  return vec_type{e(Is)...};
}

template <typename vec_type, ::std::size_t ..., typename U, ::size_t M, typename E>
typename ::std::enable_if<is_vector<E>{}, vec_type const&>::type
get_vec(vector_expression<U, M, E> const& e)
{
  return *reinterpret_cast<vec_type const*>(
    static_cast<vector<U, M> const&>(e).cdata());
}

template <typename vec_type, ::std::size_t ..., typename U, ::size_t M, typename E>
typename ::std::enable_if<is_vector<E>{}, vec_type&>::type
get_vec(vector_expression<U, M, E>& e)
{
  return *reinterpret_cast<vec_type*>(
    static_cast<vector<U, M>&>(e).data());
}

template <typename T, ::std::size_t N, typename E>
struct vector_expression
{
public:
  using size_type = ::std::size_t;
  using value_type = T;

  using reference = value_type&;
  using const_reference = value_type const&;

  static constexpr auto dimension = N;

  constexpr value_type operator()(size_type const i) const
  {
    return static_cast<E const&>(*this)(i);
  }

  operator E&() { return static_cast<E&>(*this); }

  constexpr operator E const&() const { return static_cast<E const&>(*this); }
};

template <typename S, typename E>
struct scalar_multiplication :
  vector_expression<typename E::value_type, E::dimension,
  scalar_multiplication<S, E> >
{
  using size_type = typename E::size_type;
  using value_type = prod_type<S, typename E::value_type>;

  static_assert(::std::is_same<S, typename E::value_type>::value, "");

  constexpr scalar_multiplication(S const u, E const& v) : v_(v), u_(u) { }

  constexpr value_type operator()(size_type const i) const
  {
    return u_ * v_(i);
  }

  private:
    E const& v_;
    S u_;
};

template <typename E>
struct vector_negation :
  vector_expression<typename E::value_type, E::dimension,
  vector_negation<E> >
{
  using size_type = typename E::size_type;
  using value_type = typename E::value_type;

  constexpr vector_negation(E const& u) : u_(u) { }

  constexpr value_type operator()(size_type const i) const
  {
    return -u_(i);
  }

  private:
    E const& u_;
};

template <typename E1, typename E2>
struct vector_addition : vector_expression<
  sum_type<typename E1::value_type, typename E2::value_type>,
  E1::dimension, vector_addition<E1, E2> >
{
  using size_type = typename E1::size_type;
  using value_type = sum_type<typename E1::value_type,
    typename E2::value_type>;

  constexpr vector_addition(E1 const& u, E2 const& v) : u_(u), v_(v) { }

  constexpr value_type operator()(size_type const i) const
  {
    return u_(i) + v_(i);
  }

  private:
    E1 const& u_;
    E2 const& v_;
};

template <typename E1, typename E2>
struct vector_subtraction : vector_expression<
  diff_type<typename E1::value_type, typename E2::value_type>,
  E1::dimension,
  vector_subtraction<E1, E2> >
{
  using size_type = typename E1::size_type;
  using value_type = diff_type<typename E1::value_type,
    typename E2::value_type>;

  constexpr vector_subtraction(E1 const& u, E2 const& v) : u_(u), v_(v) { }

  constexpr value_type operator()(value_type const i) const
  {
    return u_(i) - v_(i);
  }

  private:
    E1 const& u_;
    E2 const& v_;
};

template <typename E, typename S,
  typename = typename ::std::enable_if<::std::is_arithmetic<S>::value>::type>
inline constexpr scalar_multiplication<S, E>
operator/(S const a, vector_expression<typename E::value_type,
  E::dimension, E> const& rhs)
{
  return scalar_multiplication<S, E>(S(1) / a, rhs);
}

template <typename E, typename S,
  typename = typename ::std::enable_if<::std::is_arithmetic<S>::value>::type>
inline constexpr scalar_multiplication<S, E>
operator/(vector_expression<typename E::value_type,
  E::dimension, E> const& lhs, S const a)
{
  return scalar_multiplication<S, E>(S(1) / a, lhs);
}

template <typename E, typename S,
  typename = typename ::std::enable_if<::std::is_arithmetic<S>::value>::type>
inline constexpr scalar_multiplication<S, E>
operator*(S const a, vector_expression<typename E::value_type,
  E::dimension, E> const& rhs)
{
  return scalar_multiplication<S, E>(a, rhs);
}

template <typename E, typename S,
  typename = typename ::std::enable_if<::std::is_arithmetic<S>::value>::type>
inline constexpr scalar_multiplication<S, E>
operator*(vector_expression<typename E::value_type,
  E::dimension, E> const& lhs, S const a)
{
  return scalar_multiplication<S, E>(a, lhs);
}

template <class E>
inline constexpr vector_negation<E>
operator-(vector_expression<typename E::value_type,
  E::dimension, E> const& rhs)
{
  return vector_negation<E>(rhs);
}

template <class E1, class E2>
inline constexpr vector_addition<E1, E2>
operator+(
  vector_expression<typename E1::value_type, E1::dimension, E1> const& lhs,
  vector_expression<typename E2::value_type, E2::dimension, E2> const& rhs)
{
  return vector_addition<E1, E2>(lhs, rhs);
}

template <class E1, class E2>
inline constexpr vector_subtraction<E1, E2>
operator-(
  vector_expression<typename E1::value_type, E1::dimension, E1> const& lhs,
  vector_expression<typename E2::value_type, E2::dimension, E2> const& rhs)
{
  return vector_subtraction<E1, E2>(lhs, rhs);
}

namespace vector_eq_impl
{
#if defined(__GNUC__) && !defined(__clang__)
  template <::std::size_t... Is, typename E1, typename E2>
  bool get(E1 const& lhs, E2 const& rhs)
  {
    using value_type =
      prod_type<typename E1::value_type, typename E2::value_type>;
    typedef value_type vec_type __attribute__ ((vector_size(
      detail::map_extent<E1::dimension>{} * sizeof(value_type))));

    auto const v(get_vec<vec_type, 0, Is...>(lhs) ==
      get_vec<vec_type, 0, Is...>(rhs));

    bool r(v[0]);

    ::std::initializer_list<int>{(r = r && v[Is], 0)...};

    return r;
  }
#else
  template <::std::size_t i, ::std::size_t... Is, typename E1, typename E2>
  inline constexpr typename ::std::enable_if<
    bool(!sizeof...(Is)), bool
  >::type
  get(E1 const& lhs, E2 const& rhs)
  {
    return lhs(i) == rhs(i);
  }

  template <::std::size_t, ::std::size_t... Is, typename E1, typename E2>
  inline constexpr typename ::std::enable_if<
    bool(sizeof...(Is)), bool
  >::type
  get(E1 const& lhs, E2 const& rhs)
  {
    return (lhs(0) == rhs(0)) && get<Is...>(lhs, rhs);
  }
#endif

  template <typename E1, typename E2, ::std::size_t... Is>
  inline constexpr typename E1::value_type
  get(E1 const& lhs, E2 const& rhs, detail::indices<Is...> const)
  {
    return get<Is...>(lhs, rhs);
  }
}

template <typename E1, typename E2>
inline constexpr bool
operator==(vector_expression<typename E1::value_type,
  E1::dimension, E1> const& lhs,
  vector_expression<typename E2::value_type,
  E2::dimension, E2> const& rhs)
{
  return vector_eq_impl::get(lhs, rhs, detail::make_indices<E1::dimension>());
}

template <typename E1, typename E2>
inline constexpr bool
operator!=(vector_expression<typename E1::value_type,
  E1::dimension, E1> const& lhs,
  vector_expression<typename E2::value_type,
  E2::dimension, E2> const& rhs)
{
  return !operator==(lhs, rhs);
}

template <typename E1>
::std::ostream& operator<<(::std::ostream& os,
  vector_expression<typename E1::value_type, E1::dimension, E1> const& u)
{
  os << '(';

  for (typename E1::size_type i(0); i != E1::dimension - 1; ++i)
  {
    os << u(i) << ", ";
  }

  os << u(E1::dimension - 1) << ')';

  return os;
}

}

#include "dot.hpp"
#include "function.hpp"

#endif // ETL_VECTOR_HPP
